﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.IO;
using Microsoft.VisualStudio.Text;
using EnvDTE;
using System.Threading;
using System.Threading.Tasks;
using RoslynProcess;
using Logging;
using Microsoft.VisualStudio.Text.Operations;
using Util;

namespace UWCSE.CompleterVSPackage
{
    class ProcessHandler : AbstractProcessHandler
    {
        //const string EXTERNAL_COMPLETER_PROG = @"C:\Users\perelman\Documents\Visual Studio 2010\Projects\SelectionAddin\CompleterProcess\bin\Debug\CompleterProcess.exe";
        const string EXTERNAL_COMPLETER_PROG_NAME = "CompleterProcess.exe";
        protected override string PathToExecutable
        {
            get { return typeof(CompleterProcess.Program).Assembly.Location; }
        }

        public ProcessHandler()
            : base(GuidList.guidCompleterVSPackagePkgString) { }

        private int completion_id = 0;

        public static void LogLineAboutCompletion(string token, string line)
        {
            Logger.PrintLineWithTime("COMPLETION " + token + ": " + line);
        }

        public void Cancel(string cancelToken)
        {
            if (IsProcessRunning)
            {
                lock (WriteToProcessLock)
                {
                    ToProcess.WriteLine("Cancel " + cancelToken);
                }
            }
            LogLineAboutCompletion(cancelToken, "CANCELED");
        }

        public delegate void ResultHandler(Result r);
        public delegate void RewriteHandler(int matchLineNum, int matchCol,
                                            int matchEndLineNum, int matchEndCol,
                                            string rewrite);
        public delegate void TypeNameHandler(string typeName);
        private class ActiveRequest
        {
            public ActiveRequest(string cancelToken,
                    ResultHandler rh, RewriteHandler rewriteHandler,
                    TypeNameHandler tnHandler)
            {
                this.CancelToken = cancelToken;
                this.ResultHandler = rh;
                this.RewriteHandler = rewriteHandler;
                this.TypeNameHandler = tnHandler;
            }
            private Boolean matchSet = false;
            private int matchLineNum, matchCol, matchEndLineNum, matchEndCol;
            public void SetMatch(int matchLineNum, int matchCol,
                                            int matchEndLineNum, int matchEndCol)
            {
                this.matchCol = matchCol;
                this.matchLineNum = matchLineNum;
                this.matchEndCol = matchEndCol;
                this.matchEndLineNum = matchEndLineNum;
                matchSet = true;
                if (RewriteString != null)
                {
                    CallRewriteHandler();
                }
            }
            public string RewriteString { get; private set; }
            public void SetRewrite(string rewrite)
            {
                this.RewriteString = rewrite;
                if (matchSet || rewrite == null)
                {
                    CallRewriteHandler();
                }
            }
            private void CallRewriteHandler()
            {
                RewriteHandler(matchLineNum, matchCol, matchEndLineNum, matchEndCol,
                    RewriteString);
            }

            public ResultHandler ResultHandler { get; private set; }
            public RewriteHandler RewriteHandler { get; private set; }
            public TypeNameHandler TypeNameHandler { get; private set; }
            public string CancelToken { get; private set; }
        }
        private Dictionary<string, ActiveRequest> handlers = new Dictionary<string, ActiveRequest>();

        public void GetCompletions(int startLine, int startCol, int endLine, int endCol,
                                   string query,
                                   Action<string> setCancelToken,
                                   RewriteHandler rewriteHandler,
                                   TypeNameHandler tnHandler,
                                   ResultHandler rh)
        {
            AssertProcessRunning();
            //ReadAllLines();
            // requires calls to LoadSolution/ChooseProject/ChooseDocument first.
            int id = completion_id++;
            string cancelToken = id.ToString();
            setCancelToken(cancelToken);
            handlers[cancelToken] = new ActiveRequest(cancelToken, rh, rewriteHandler, tnHandler);
            LogLineAboutCompletion(cancelToken, "START REQUEST");
            string request = "Complete " + startLine + " " + startCol + " "
                + endLine + " " + endCol + " " + id;
            if (query != null)
            {
                request += " " + query;
            }
            lock (WriteToProcessLock)
            {
                ToProcess.WriteLine(request);
            }
            LogLineAboutCompletion(cancelToken, "REQUEST: " + request);
        }

        private static readonly char[] spaceArr = new char[] {' '};
        protected override bool HandleLine(string line)
        {
            if (line.StartsWith("FAILURE"))
            {
                Logger.PrintLineWithTime(line);
                string[] failArr = line.Split(spaceArr, 3);
                ActiveRequest ar;
                if (handlers.TryGetValue(failArr[1], out ar))
                {
                    handlers.Remove(failArr[1]);
                    ar.ResultHandler(null);
                }
                return true;
            }
            else if (line.StartsWith("COMPLETION: "))
            {
                string[] compArr = line.Split(spaceArr, 3);
                string compId = compArr[1];
                ActiveRequest ar;
                if (handlers.TryGetValue(compId, out ar))
                {
                    ar.ResultHandler(new Result(compArr[2]));
                }
                return true;
            }
            else if (line.StartsWith("FINISHED_COMPLETIONS: "))
            {
                string[] compArr = line.Split(spaceArr);
                string compId = compArr[1];
                ActiveRequest ar;
                if (handlers.TryGetValue(compId, out ar))
                {
                    ar.ResultHandler(null); // Special value meaning to remove "Loading..."
                    // This is the last message.
                    handlers.Remove(compId);
                }
                LogLineAboutCompletion(compId, line);
                return true;
            }
            else if (line.StartsWith("MATCH_TYPE: "))
            {
                string[] typeArr = line.Split(spaceArr, 3);
                if (typeArr.Length == 3)
                {
                    string compId = typeArr[1];
                    ActiveRequest ar;
                    if (handlers.TryGetValue(compId, out ar))
                    {
                        string typeStr = typeArr[2];
                        ar.TypeNameHandler(typeStr);
                    }
                    LogLineAboutCompletion(compId, line);
                }
                else
                {
                    Logger.PrintLineWithTime("Invalid MATCH_TYPE line: " + line);
                }
                return true;
            }
            else if (line.StartsWith("MATCH_SPAN: "))
            {
                string[] matchSpanArr = line.Split(' ');
                if (matchSpanArr.Length == 6)
                {
                    string compId = matchSpanArr[1];
                    ActiveRequest ar;
                    if (handlers.TryGetValue(compId, out ar))
                    {
                        int matchLineNum = int.Parse(matchSpanArr[2]);
                        int matchCol = int.Parse(matchSpanArr[3]);
                        int matchEndLineNum = int.Parse(matchSpanArr[4]);
                        int matchEndCol = int.Parse(matchSpanArr[5]);

                        ar.SetMatch(matchLineNum, matchCol, matchEndLineNum, matchEndCol);
                    }
                    LogLineAboutCompletion(compId, line);
                }
                else
                {
                    Logger.PrintLineWithTime("Invalid MATCH_SPAN line: " + line);
                }
                return true;
            }
            else if (line.StartsWith("MATCH_REWRITE: "))
            {
                string[] rewriteArr = line.Split(spaceArr, 3);
                if (rewriteArr.Length == 3)
                {
                    string compId = rewriteArr[1];
                    ActiveRequest ar;
                    if (handlers.TryGetValue(compId, out ar))
                    {
                        ar.SetRewrite(rewriteArr[2]);
                    }
                }
                else
                {
                    Logger.PrintLineWithTime("Invalid MATCH_REWRITE line: " + line);
                }
                return true;
            }
            else if (line.StartsWith("MATCH_NOT_PARTIAL: "))
            {
                string[] notPartialArr = line.Split(spaceArr, 2);
                string compId = notPartialArr[1];
                ActiveRequest ar;
                if (handlers.TryGetValue(compId, out ar))
                {
                    ar.SetRewrite(null);
                }
                LogLineAboutCompletion(compId, line);
                return true;
            }
            else if (line.StartsWith("CANCELLED"))
            {
                handlers.Remove(line.Split(spaceArr)[1]);
                return true;
            }
            else
            {
                return false;
            }
        }

        internal void RequestMoreCompletionsAsync(string idStr,
            CancellationToken cancellationToken, int numSeen)
        {
            if (idStr == null || idStr.Length == 0)
            {
                Logger.PrintLineWithTime("RequestMoreCompletionsAsync: Invalid idStr: " + (idStr == null ? null : "\"" + idStr + "\""));
                throw new Exception();
            }
            else
            {
                Task.Factory.StartNew(() =>
                {
                    string request = "MoreCompletions " + idStr + " " + numSeen;
                    ToProcess.WriteLine(request);
                    LogLineAboutCompletion(idStr, "REQUEST: " + request);
                }, cancellationToken);
            }
        }
    }
}
